webpack 是一个现代 JavaScript 应用程序最为火热的静态模块打包器,当 webpack 处理应用程序时,会递归构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle ,在项目构建的时候可以根据项目需求配置自己的构建配置来优化构建的速度和项目的加载速度。
我们在这篇文章中主要来说说优化webpack项目构建性能,和对于项目的优化为目的,罗列一些常见的webpack优化方案(配置方面优化 , 优化插件的使用)
需要优化,是否先得知道当前构建工作流中还存在什么问题吧?
1. 使用 webpack-bundle-analyzer
来查看构建出来的项目具体情况
cnpm install webpack-bundle-analyzer -D
//webpack.config.prod.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.config.base');
module.exports = merge(baseWebpackConfig, {
//....
plugins: [
//...
new BundleAnalyzerPlugin(),
]
})
npm run build 构建,会默认打开: http://127.0.0.1:8888/,可以看到各个包的体积
2. 使用 webpack-jarvis
相比上面的插件更为直观些(个人觉得)同样可以查看出所有构建的项目在构建的时间和各个chunk的细节。
const Jarvis = require("webpack-jarvis");
module.exports = merge(baseWebpackConfig, {
//....
plugins: [
new Jarvis({
watchOnly: true,//仅仅监听编译阶段
port: 3001 // 会开启一个web ,需要设置查看的端口
})
]
})
3. 使用`speed-measure-webpack-plugin
来测量各个插件和loader所花费的时间
插件的使用方法很简单,直接将webpack的配置包裹起来即可。
//webpack.config.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const config = {
//...webpack配置
}
module.exports = smp.wrap(config);
下面进入优化的实施细节:
1.exclude/include
我们可以通过 exclude、include 配置来确保转译尽可能少的文件。顾名思义,exclude 指定要排除的文件,include 指定要包含的文件。 exclude 的优先级高于 include,在 include 和 exclude 中使用绝对路径数组,尽量避免 exclude,更倾向于使用 include。
//webpack.config.js
const path = require('path');
module.exports = {
//...
module: {
rules: [
{
test: /\.js[x]?$/,
use: ['babel-loader'],
include: [path.resolve(__dirname, 'src')]
}
]
},
}
2. cache-loader
在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。默认保存在 node_modueles/.cache/cache-loader 目录下
//按照正常的loader配置即可
如果只需要配置babel-loader , 可以直接设置 cacheDirectory:true 即可
3.happypack
由于有大量文件需要解析和处理,构建是文件读写和计算密集型的操作,特别是当文件数量变多后,Webpack 构建慢的问题会显得严重。文件读写和计算操作是无法避免的,那能不能让 Webpack 同一时刻处理多个任务,发挥多核 CPU 电脑的威力,以提升构建速度呢? HappyPack 就能让 Webpack 做到这点,它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。
const Happypack = require('happypack');
module.exports = {
//...
module: {
rules: [
{
test: /\.js[x]?$/,
use: 'Happypack/loader?id=js',
include: [path.resolve(__dirname, 'src')]
},
{
test: /\.css$/,
use: 'Happypack/loader?id=css',
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'node_modules', 'bootstrap', 'dist')
]
}
]
},
plugins: [
new Happypack({
id: 'js', //和rule中的id=js对应
//将之前 rule 中的 loader 在此配置
use: ['babel-loader'] //必须是数组
}),
new Happypack({
id: 'css',//和rule中的id=css对应
use: ['style-loader', 'css-loader','postcss-loader'],
})
]
}
4.HardSourceWebpackPlugin
HardSourceWebpackPlugin 为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source。 配置 hard-source-webpack-plugin,首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。
var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
//...
plugins: [
new HardSourceWebpackPlugin()
]
}
5.noParse
如果一些第三方模块没有AMD/CommonJS规范版本,可以使用 noParse 来标识这个模块,这样 Webpack 会引入这些模块,但是不进行转化和解析,从而提升 Webpack 的构建性能 ,例如:jquery 、lodash。
//webpack.config.js
module.exports = {
//...
module: {
noParse: /jquery|lodash/
}
}
6.resolve中的 modules , extensions
resolve 配置 webpack 如何寻找模块所对应的文件 , modules是指定查找依赖模块的目录(默认就是node_modules ,不建议修改) ,extensions 是指定当没有指定文件扩展名的时候,该怎么去匹配文件,默认是['js','json'] ,就是说默认先找js文件 ,找不到再使用json ,可以按照项目文件的出现概率去设置。
7.IgnorePlugin 忽略第三方包可能导入的非必须依赖
//webpack.config.js
module.exports = {
//...
plugins: [
//忽略 moment 下的 ./locale 目录
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
}
//以上配置 会在以下方式导入包的时候,只引入手动引入的包
import moment from 'moment';
import 'moment/locale/zh-cn';// 手动引入
8.项目生产环境CDN 优化 - externals
经常使用一些第三方的库的时候,我们可以采用CDN方式来加速和降低本地服务器的压力,同样我们也可以使用在webpack中,那么久需要指定一些第三方的库不需要打包出来,我们后期可以通过cdn 在网页中引入即可。
//webpack.config.js
module.exports = {
//...
externals: {
//jquery通过script引入之后,全局中即有了 jQuery 变量
'jquery': 'jQuery'
}
}
9.抽离公共代码
抽离公共代码是对于多页应用来说的,如果多个页面引入了一些公共模块,那么可以把这些公共的模块抽离出来,单独打包。公共代码只需要下载一次就缓存起来了,避免了重复下载。
//webpack.config.js
module.exports = {
optimization: {
splitChunks: {//分割代码块
cacheGroups: {
vendor: {
//第三方依赖
priority: 1, //设置优先级,首先抽离第三方模块
name: 'vendor',
test: /node_modules/,
chunks: 'initial',
minSize: 0,
minChunks: 1 //最少引入了1次
},
//缓存组
common: {
//公共模块
chunks: 'initial',
name: 'common',
minSize: 100, //大小超过100个字节
minChunks: 3 //最少引入了3次
}
}
}
},
runtimeChunk: {//runtimeChunk 的作用是将包含 chunk 映射关系的列表从 main.js 中抽离出来 生成一个mainfest.js 文件
name: 'manifest'
}
}
10. webpack 4.x 自身的优化
tree-shaking 如果使用ES6的import 语法,那么在生产环境下,会自动移除没有使用到的代码
scope hosting 作用域提升,可以减少一些变量声明。在生产环境下,默认开启。